/**
 * Copyright (C) 2010-2016 eBusiness Information, Excilys Group
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed To in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package org.ohosannotations.holder;


import com.helger.jcodemodel.AbstractJClass;
import com.helger.jcodemodel.EClassType;
import com.helger.jcodemodel.JCodeModel;
import com.helger.jcodemodel.JDefinedClass;
import com.helger.jcodemodel.JTypeVar;

import org.ohosannotations.OhosAnnotationsEnvironment;
import org.ohosannotations.Option;
import org.ohosannotations.helper.APTCodeModelHelper;
import org.ohosannotations.internal.process.ProcessHolder;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;

import static com.helger.jcodemodel.JMod.FINAL;
import static com.helger.jcodemodel.JMod.PUBLIC;
import static com.helger.jcodemodel.JMod.STATIC;
import static org.ohosannotations.helper.ModelConstants.classSuffix;

/**
 * 生成的基类持有人
 *
 * @author dev
 * @since 2021-06-03
 */
public abstract class BaseGeneratedClassHolder implements GeneratedClassHolder {
    /**
     * 选择生成最终的类
     */
    public static final Option OPTION_GENERATE_FINAL_CLASSES = new Option("generateFinalClasses", "true");
    /**
     * 环境
     */
    protected final OhosAnnotationsEnvironment environment;
    /**
     * 生成的类
     */
    protected JDefinedClass generatedClass;
    /**
     * 带注释的类
     */
    protected AbstractJClass annotatedClass;
    /**
     * 带注释的元素
     */
    protected final TypeElement annotatedElement;
    /**
     * 代码模型辅助
     */
    protected final APTCodeModelHelper codeModelHelper;
    private Map<Class<?>, Object> pluginHolders = new HashMap<>();

    /**
     * BaseGeneratedClassHolder
     *
     * @param environment environment
     * @param annotatedElement annotatedElement
     * @throws Exception
     */
    public BaseGeneratedClassHolder(OhosAnnotationsEnvironment environment,
        TypeElement annotatedElement) throws Exception {
        this.environment = environment;
        this.annotatedElement = annotatedElement;
        codeModelHelper = new APTCodeModelHelper(environment);
        setGeneratedClass();
    }

    /**
     * setGeneratedClass
     *
     * @throws Exception
     */
    protected void setGeneratedClass() throws Exception {
        String annotatedComponentQualifiedName = annotatedElement.getQualifiedName().toString();
        annotatedClass = getCodeModel().directClass(annotatedElement.asType().toString());

        if (annotatedElement.getNestingKind().isNested()) {
            Element enclosingElement = annotatedElement.getEnclosingElement();
            GeneratedClassHolder enclosingHolder = environment.getGeneratedClassHolder(enclosingElement);
            String generatedBeanSimpleName = annotatedElement.getSimpleName().toString() + classSuffix();
            int modifier = PUBLIC | STATIC;
            if (environment.getOptionBooleanValue(OPTION_GENERATE_FINAL_CLASSES)) {
                modifier |= FINAL;
            }
            generatedClass = enclosingHolder.getGeneratedClass()
                ._class(modifier, generatedBeanSimpleName, EClassType.CLASS);
        } else {
            String generatedClassQualifiedName = annotatedComponentQualifiedName + classSuffix();
            int modifier = PUBLIC;
            if (environment.getOptionBooleanValue(OPTION_GENERATE_FINAL_CLASSES)) {
                modifier |= FINAL;
            }
            generatedClass = getCodeModel()._class(modifier, generatedClassQualifiedName, EClassType.CLASS);
        }
        codeModelHelper.generify(generatedClass, annotatedElement);
        setExtends();
        codeModelHelper.copyNonAAAnnotations(generatedClass, annotatedElement.getAnnotationMirrors());
    }

    /**
     * getAnnotatedClass
     *
     * @return annotatedClass
     */
    protected AbstractJClass getAnnotatedClass() {
        return annotatedClass;
    }

    /**
     * setExtends
     */
    protected void setExtends() {
        AbstractJClass annotatedComponent = getCodeModel()
            .directClass(annotatedElement.asType().toString());
        generatedClass._extends(annotatedComponent);
    }

    /**
     * getGeneratedClass
     *
     * @return generatedClass
     */
    @Override
    public JDefinedClass getGeneratedClass() {
        return generatedClass;
    }

    /**
     * getAnnotatedElement
     *
     * @return annotatedElement
     */
    @Override
    public TypeElement getAnnotatedElement() {
        return annotatedElement;
    }

    /**
     * getEnvironment
     *
     * @return environment
     */
    @Override
    public OhosAnnotationsEnvironment getEnvironment() {
        return environment;
    }

    /**
     * getProcessingEnvironment
     *
     * @return environment
     */
    protected ProcessingEnvironment getProcessingEnvironment() {
        return environment.getProcessingEnvironment();
    }

    /**
     * getClasses
     *
     * @return environment
     */
    protected ProcessHolder.Classes getClasses() {
        return environment.getClasses();
    }

    /**
     * getCodeModel
     *
     * @return getEnvironment
     */
    protected JCodeModel getCodeModel() {
        return getEnvironment().getCodeModel();
    }

    /**
     * getJClass
     *
     * @param fullyQualifiedClassName fullyQualifiedClassName
     * @return getEnvironment
     */
    protected AbstractJClass getJClass(String fullyQualifiedClassName) {
        return getEnvironment().getJClass(fullyQualifiedClassName);
    }

    /**
     * getJClass
     *
     * @param clazz clazz
     * @return getEnvironment
     */
    protected AbstractJClass getJClass(Class<?> clazz) {
        return getEnvironment().getJClass(clazz);
    }

    /**
     * narrow
     *
     * @param toNarrow toNarrow
     * @return toNarrow
     */
    public AbstractJClass narrow(AbstractJClass toNarrow) {
        List<AbstractJClass> classes = new ArrayList<>();
        for (JTypeVar type : generatedClass.typeParams()) {
            classes.add(getCodeModel().directClass(type.name()));
        }
        if (classes.isEmpty()) {
            return toNarrow;
        }
        return toNarrow.narrow(classes);
    }

    /**
     * getPluginHolder
     *
     * @param pluginHolder pluginHolder
     * @param <T> T
     * @return currentPluginHolder
     */
    @SuppressWarnings("unchecked")
    public <T> T getPluginHolder(T pluginHolder) {
        T currentPluginHolder = (T) pluginHolders.get(pluginHolder.getClass());
        if (currentPluginHolder == null) {
            currentPluginHolder = pluginHolder;
            pluginHolders.put(pluginHolder.getClass(), pluginHolder);
        }
        return currentPluginHolder;
    }
}
